package com.google.ad.pay

import android.app.Activity
import android.app.Application
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import com.android.billingclient.api.AcknowledgePurchaseParams
import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.BillingClientStateListener
import com.android.billingclient.api.BillingFlowParams
import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.ConsumeParams
import com.android.billingclient.api.ProductDetails
import com.android.billingclient.api.Purchase
import com.android.billingclient.api.PurchasesUpdatedListener
import com.android.billingclient.api.QueryProductDetailsParams
import com.android.billingclient.api.QueryProductDetailsParams.Product
import com.android.billingclient.api.QueryPurchasesParams
import com.android.billingclient.api.acknowledgePurchase
import com.android.billingclient.api.consumePurchase
import com.google.ad.pay.PaymentHelper.logger

import com.google.ad.pay.PaymentHelper.vipSku
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.LinkedList
import kotlin.collections.set
import kotlin.math.min

/**
 * @author Afra55
 * @date 2023/5/8
 * A smile is the best business card.
 * 当用户订阅后，会发 EVENT_NEW_PURCHASE 事件
 */
private const val RECONNECT_TIMER_START_MILLISECONDS = 1L * 1000L
private const val RECONNECT_TIMER_MAX_TIME_MILLISECONDS = 1000L * 60L * 15L // 15 minutes
//private const val SKU_DETAILS_REQUERY_TIME = 1000L * 60L * 60L * 4L // 4 hours

object PaymentHelper {

    fun logger(msg: String) {
        Log.d("GOOGLE_PAY", msg)
    }

    var paymentManager: PaymentManager? = null

    const val SKU_SUB_MONTH_1 = "com.flixtools.movies.app.month"
    const val SKU_SUB_MONTH_3 = "com.flixtools.movies.app.3month"
    const val SKU_SUB_MONTH_12 = "com.flixtools.movies.app.year"
    const val SKU_SUB_MONTH_12_NEW = "com.flixtools.movies.app.annual"

    var vipSku = hashMapOf<String, Long>()

    fun isVip(): Boolean {
        return vipSku.isNotEmpty()
    }


    /**
     * 必须初始化
     */
    fun init(application: Application) {
        val knownInappSKUs = arrayListOf<String>()
        val knownSubscriptionSKUs = arrayListOf<String>()
        knownSubscriptionSKUs.add(SKU_SUB_MONTH_1)
        knownSubscriptionSKUs.add(SKU_SUB_MONTH_3)
        knownSubscriptionSKUs.add(SKU_SUB_MONTH_12)
        knownSubscriptionSKUs.add(SKU_SUB_MONTH_12_NEW)
        paymentManager = PaymentManager(application, knownInappSKUs, knownSubscriptionSKUs)

        ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
            override fun onResume(owner: LifecycleOwner) {
                super.onResume(owner)
                try {
                    paymentManager?.resume()
                } catch (e: Exception) {
                    // nothing
                }
            }
        })
    }

    fun setPaymentListener(paymentListener: PaymentListener?) {
        paymentManager?.PaymentListener = paymentListener

    }
    fun canPay(sku: String): Boolean {
        return paymentManager?.canPurchase(sku) ?: false
    }

    fun isPurchased(sku: String): Boolean {
        return paymentManager?.isPurchased(sku) ?: false
    }

    data class SkuDetail internal constructor(
        val sku: String,
        val title: String?,
        val description: String?,
        val price: String?,
        val priceAmountMicros: Long?,
        val priceCurrencyCode: String?
    )

    fun getSkuDetail(sku: String): SkuDetail {
        return SkuDetail(
            sku,
            paymentManager?.getSkuTitle(sku),
            paymentManager?.getSkuDescription(sku),
            paymentManager?.getSkuPrice(sku),
            paymentManager?.getSkuPriceAmountMicros(sku),
            paymentManager?.getSkuPriceCurrencyCode(sku)
        )
    }

    fun toPay(activity: Activity, sku: String) {
        paymentManager?.launchBillingFlow(activity, sku)
    }


}

/**
 * @param application
 * @param knownInappSKUs 一次性消耗产品 sku
 * @param knownSubscriptionSKUs 订阅产品 sku
 * @param knownAutoConsumeSKUs 购买一次性的产品后，自动消耗订单的sku
 *
 */
class PaymentManager(
    val application: Application,
    val knownInappSKUs: List<String>? = null,
    val knownSubscriptionSKUs: List<String>? = null,
    val knownAutoConsumeSKUs: List<String>? = null,
) : BillingClientStateListener, PurchasesUpdatedListener {
    enum class SkuState {
        SKU_STATE_UNPURCHASED, SKU_STATE_PENDING, SKU_STATE_PURCHASED, SKU_STATE_PURCHASED_AND_ACKNOWLEDGED
    }

    var PaymentListener:PaymentListener? = null

    private var billingClient = BillingClient.newBuilder(application)
        .setListener(this)
        .enablePendingPurchases()
        .build()

    /**
     * 从谷歌服务器抓取的所有订单详情
     */
    val skuDetailsMap = hashMapOf<String, ProductDetails>()

    /**
     * 订单状态,默认
     * @see SkuState.SKU_STATE_UNPURCHASED
     */
    val skuStateMap = hashMapOf<String, SkuState>()

    // Observables that are used to communicate state.
    private val purchaseConsumptionInProcess: MutableSet<Purchase> = HashSet()

    var billingFlowInProcess = false

    init {

        billingClient.startConnection(this)
    }


    private val handler by lazy { Handler(Looper.getMainLooper()) }

    // how long before the data source tries to reconnect to Google play
    private var reconnectMilliseconds = RECONNECT_TIMER_START_MILLISECONDS

    /**
     * 重试链接谷歌服务，
     * Retries the billing service connection with exponential backoff, maxing out at the time
     * specified by RECONNECT_TIMER_MAX_TIME_MILLISECONDS.
     */
    private fun retryBillingServiceConnectionWithExponentialBackoff() {
        try {
            handler.postDelayed(
                { billingClient.startConnection(this) },
                reconnectMilliseconds
            )
            reconnectMilliseconds = min(
                reconnectMilliseconds * 2,
                RECONNECT_TIMER_MAX_TIME_MILLISECONDS
            )
        } catch (e: Exception) {
            e.printStackTrace()

        }
    }

    override fun onBillingServiceDisconnected() {
        // Try to restart the connection on the next request to
        // Google Play by calling the startConnection() method.
        retryBillingServiceConnectionWithExponentialBackoff()
    }

    /**
     * 谷歌服务链接成功
     */
    override fun onBillingSetupFinished(billingResult: BillingResult) {
        // To be implemented in a later section.
        if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
            // The BillingClient is ready. You can query purchases here.
            // The billing client is ready. You can query purchases here.
            // This doesn't mean that your app is set up correctly in the console -- it just
            // means that you have a connection to the Billing service.
            reconnectMilliseconds = RECONNECT_TIMER_START_MILLISECONDS

            CoroutineScope(Dispatchers.IO).launch {
                querySkuDetailsAsync()
            }

        } else {
            retryBillingServiceConnectionWithExponentialBackoff()
        }
    }

    /**
     * 查询所有订单信息
     * Calls the billing client functions to query sku details for both the inapp and subscription
     * SKUs. SKU details are useful for displaying item names and price lists to the user, and are
     * required to make a purchase.
     */
    private fun querySkuDetailsAsync() {
        try {
            if (!knownInappSKUs.isNullOrEmpty()) {
                val productList = arrayListOf<Product>()
                for (i in knownInappSKUs) {
                    productList.add(
                        Product.newBuilder()
                            .setProductId(i)
                            .setProductType(BillingClient.ProductType.INAPP)
                            .build()
                    )
                }
                val queryProductDetailsParams =
                    QueryProductDetailsParams.newBuilder()
                        .setProductList(productList)
                        .build()
                billingClient.queryProductDetailsAsync(
                    queryProductDetailsParams
                ) { billingResult, productDetailsList ->
                    onSkuDetailsResponse(billingResult, productDetailsList)
                    refreshPurchases(knownInappSKUs, BillingClient.ProductType.INAPP)
                }


            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
        try {
            if (!knownSubscriptionSKUs.isNullOrEmpty()) {
                val productList = arrayListOf<Product>()
                for (i in knownSubscriptionSKUs) {
                    productList.add(
                        Product.newBuilder()
                            .setProductId(i)
                            .setProductType(BillingClient.ProductType.SUBS)
                            .build()
                    )
                }
                val queryProductDetailsParams =
                    QueryProductDetailsParams.newBuilder()
                        .setProductList(productList)
                        .build()
                billingClient.queryProductDetailsAsync(
                    queryProductDetailsParams
                ) { billingResult, productDetailsList ->
                    onSkuDetailsResponse(billingResult, productDetailsList)
                    refreshPurchases(knownSubscriptionSKUs, BillingClient.ProductType.SUBS)
                }

            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    /**
    查询订单状态
    @param skus
    @param productType BillingClient.ProductType.SUBS
     */
    fun refreshPurchases(skus: List<String>, productType: String) {
        if (skus.isEmpty()) {
            logger("empty skus $productType.")
            return
        }
        logger("Refreshing purchases $productType.")
        try {
            val queryProductDetailsParams =
                QueryPurchasesParams.newBuilder()
                    .setProductType(productType)
                    .build()
            billingClient.queryPurchasesAsync(
                queryProductDetailsParams
            ) { billingResult, purchasesList ->
                if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
                    logger("Problem getting purchases: " + billingResult.debugMessage)
                } else {
                    processPurchaseList(purchasesList, knownInappSKUs)
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
        logger("Refreshing purchases finished $productType.")
    }


    private fun processPurchaseList(purchases: List<Purchase>?, skusToUpdate: List<String>?) {
        val updatedSkus = HashSet<String>()
        if (null != purchases) {
            for (purchase in purchases) {
                for (sku in purchase.products) {
                    val skuStateFlow = skuStateMap[sku]
                    if (null == skuStateFlow) {
                        logger(
                            "Unknown SKU " + sku + ". Check to make " +
                                    "sure SKU matches SKUS in the Play developer console."
                        )
                        continue
                    }
                    updatedSkus.add(sku)
                }
                // Global check to make sure all purchases are signed correctly.
                // This check is best performed on your server.
                val purchaseState = purchase.purchaseState
                if (purchaseState == Purchase.PurchaseState.PURCHASED) {
                    if (!isSignatureValid(purchase)) {
                        logger(
                            "Invalid signature. Check to make sure your " +
                                    "public key is correct."
                        )
                        continue
                    }
                    // only set the purchased state after we've validated the signature.
                    setSkuStateFromPurchase(purchase)
                    var isConsumable = false
                    CoroutineScope(Dispatchers.IO).launch {
                        for (sku in purchase.products) {
                            if (knownAutoConsumeSKUs?.contains(sku) == true) {
                                isConsumable = true
                            } else {
                                if (isConsumable) {
                                    logger(
                                        "Purchase cannot contain a mixture of consumable" +
                                                "and non-consumable items: " + purchase.products.toString()
                                    )
                                    isConsumable = false
                                    break
                                }
                            }
                        }
                        if (isConsumable) {
                            consumePurchase(purchase)
                            logger("purchase_success_from_google_consume")
                            PaymentListener?.purchaseSuccess()
                        } else if (!purchase.isAcknowledged) {
                            // acknowledge everything --- new purchases are ones not yet acknowledged

                            val billingResult = billingClient.acknowledgePurchase(
                                AcknowledgePurchaseParams.newBuilder()
                                    .setPurchaseToken(purchase.purchaseToken)
                                    .build()
                            )
                            if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
                                logger(
                                    "Error acknowledging purchase: ${purchase.products}"
                                )
                            } else {
                                // purchase acknowledged
                                for (sku in purchase.products) {
                                    setSkuState(
                                        sku,
                                        SkuState.SKU_STATE_PURCHASED_AND_ACKNOWLEDGED
                                    )
                                }
                                logger("purchase_success_from_google_subs")
                                PaymentListener?.purchaseSuccess()
                            }
                        }
                    }
                } else {
                    // make sure the state is set
                    setSkuStateFromPurchase(purchase)
                }
            }
        } else {
            logger("Empty purchase list.")
        }
        // Clear purchase state of anything that didn't come with this purchase list if this is
        // part of a refresh.
        if (null != skusToUpdate) {
            for (sku in skusToUpdate) {
                if (!updatedSkus.contains(sku)) {
                    setSkuState(sku, SkuState.SKU_STATE_UNPURCHASED)
                }
            }
        }
    }


    /**
     * 消耗订单
     * Internal call only. Assumes that all signature checks have been completed and the purchase
     * is ready to be consumed. If the sku is already being consumed, does nothing.
     * @param purchase purchase to consume
     */
    private suspend fun consumePurchase(purchase: Purchase) {
        // weak check to make sure we're not already consuming the sku
        if (purchaseConsumptionInProcess.contains(purchase)) {
            // already consuming
            return
        }
        purchaseConsumptionInProcess.add(purchase)
        val consumePurchaseResult = billingClient.consumePurchase(
            ConsumeParams.newBuilder()
                .setPurchaseToken(purchase.purchaseToken)
                .build()
        )

        purchaseConsumptionInProcess.remove(purchase)
        if (consumePurchaseResult.billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
            logger("Consumption successful. Emitting sku.")
            // Since we've consumed the purchase
            for (sku in purchase.products) {
                setSkuState(sku, SkuState.SKU_STATE_UNPURCHASED)
            }
        } else {
            logger("Error while consuming: ${consumePurchaseResult.billingResult.debugMessage}")
        }
    }

    /**
     * 设置订单状态
     * Since we (mostly) are getting sku states when we actually make a purchase or update
     * purchases, we keep some internal state when we do things like acknowledge or consume.
     * @param sku product ID to change the state of
     * @param newSkuState the new state of the sku.
     */
    private fun setSkuState(sku: String, newSkuState: SkuState) {
        val skuStateFlow = skuStateMap[sku]
        if (skuStateFlow != null) {
            skuStateMap[sku] = newSkuState
        } else {
            logger(
                "Unknown SKU " + sku + ". Check to make " +
                        "sure SKU matches SKUS in the Play developer console."
            )
        }
    }

    /**
     * 设置订单状态
     * Calling this means that we have the most up-to-date information for a Sku in a purchase
     * object. This uses the purchase state (Pending, Unspecified, Purchased) along with the
     * acknowledged state.
     * @param purchase an up-to-date object to set the state for the Sku
     */
    private fun setSkuStateFromPurchase(purchase: Purchase) {
        for (purchaseSku in purchase.products) {
            val skuStateFlow = skuStateMap[purchaseSku]
            if (null == skuStateFlow) {
                logger(
                    "Unknown SKU " + purchaseSku + ". Check to make " +
                            "sure SKU matches SKUS in the Play developer console."
                )
            } else {
                when (purchase.purchaseState) {
                    Purchase.PurchaseState.PENDING -> skuStateMap[purchaseSku] =
                        (SkuState.SKU_STATE_PENDING)

                    Purchase.PurchaseState.UNSPECIFIED_STATE -> skuStateMap[purchaseSku] =
                        (SkuState.SKU_STATE_UNPURCHASED)

                    Purchase.PurchaseState.PURCHASED -> if (purchase.isAcknowledged) {
                        skuStateMap[purchaseSku] = (SkuState.SKU_STATE_PURCHASED_AND_ACKNOWLEDGED)
                        vipSku[purchaseSku] = purchase.purchaseTime
                    } else {
                        skuStateMap[purchaseSku] = (SkuState.SKU_STATE_PURCHASED)
                    }

                    else -> logger("Purchase in unknown state: " + purchase.purchaseState)
                }
            }
        }
    }

    /**
     * Ideally your implementation will comprise a secure server, rendering this check
     * unnecessary. @see [Security]
     */
    @Suppress("UNUSED_PARAMETER")
    private fun isSignatureValid(purchase: Purchase): Boolean {
//        return Security.verifyPurchase(purchase.originalJson, purchase.signature)
        return true
    }

    /**
     * 获取订单详情，初始化订单状态
     * Receives the result from [.querySkuDetailsAsync]}.
     *

     */
    private fun onSkuDetailsResponse(
        billingResult: BillingResult,
        skuDetailsList: List<ProductDetails>?
    ) {
        val responseCode = billingResult.responseCode
        val debugMessage = billingResult.debugMessage
        when (responseCode) {
            BillingClient.BillingResponseCode.OK -> {
                logger("onSkuDetailsResponse: $responseCode $debugMessage")
                if (skuDetailsList == null || skuDetailsList.isEmpty()) {
                    logger(
                        "onSkuDetailsResponse: " +
                                "Found null or empty SkuDetails. " +
                                "Check to see if the SKUs you requested are correctly published " +
                                "in the Google Play Console."
                    )
                } else {
                    for (skuDetails in skuDetailsList) {
                        val sku = skuDetails.productId
                        skuDetailsMap[sku] = skuDetails
                        skuStateMap[sku] = SkuState.SKU_STATE_UNPURCHASED
                    }
                }
            }

            BillingClient.BillingResponseCode.SERVICE_DISCONNECTED,
            BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE,
            BillingClient.BillingResponseCode.BILLING_UNAVAILABLE,
            BillingClient.BillingResponseCode.ITEM_UNAVAILABLE,
            BillingClient.BillingResponseCode.DEVELOPER_ERROR,
            BillingClient.BillingResponseCode.ERROR ->
                logger("onSkuDetailsResponse: $responseCode $debugMessage")

            BillingClient.BillingResponseCode.USER_CANCELED ->
                logger("onSkuDetailsResponse: $responseCode $debugMessage")

            BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED,
            BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED,
            BillingClient.BillingResponseCode.ITEM_NOT_OWNED ->
                logger("onSkuDetailsResponse: $responseCode $debugMessage")

            else -> logger("onSkuDetailsResponse: $responseCode $debugMessage")
        }

    }

    /**
     * 获取订单标题
     */
    fun getSkuTitle(sku: String): String? {
        return skuDetailsMap[sku]?.title
    }

    fun getSkuPriceCurrencyCode(sku: String): String? {
        val productDetails = skuDetailsMap[sku] ?: return null
        return try {
            if (productDetails.productType == BillingClient.ProductType.SUBS) {
                productDetails.subscriptionOfferDetails?.get(0)?.pricingPhases?.pricingPhaseList?.get(
                    0
                )?.priceCurrencyCode
            } else {
                productDetails.oneTimePurchaseOfferDetails?.priceCurrencyCode
            }
        } catch (e: Exception) {
            null
        }
    }

    fun getSkuPriceAmountMicros(sku: String): Long? {
        val productDetails = skuDetailsMap[sku] ?: return null
        return try {
            if (productDetails.productType == BillingClient.ProductType.SUBS) {
                productDetails.subscriptionOfferDetails?.get(0)?.pricingPhases?.pricingPhaseList?.get(
                    0
                )?.priceAmountMicros
            } else {
                productDetails.oneTimePurchaseOfferDetails?.priceAmountMicros
            }
        } catch (e: Exception) {
            null
        }
    }

    /**
     * 获取订单价格
     */
    fun getSkuPrice(sku: String): String? {
        val productDetails = skuDetailsMap[sku] ?: return null
        return try {
            if (productDetails.productType == BillingClient.ProductType.SUBS) {
                productDetails.subscriptionOfferDetails?.get(0)?.pricingPhases?.pricingPhaseList?.get(
                    0
                )?.formattedPrice
            } else {
                productDetails.oneTimePurchaseOfferDetails?.formattedPrice
            }
        } catch (e: Exception) {
            null
        }
    }

    /**
     * 获取订单描述
     */
    fun getSkuDescription(sku: String): String? {
        return skuDetailsMap[sku]?.description
    }

    /**
     * 启动购买流程
     * Launch the billing flow. This will launch an external Activity for a result, so it requires
     * an Activity reference. For subscriptions, it supports upgrading from one SKU type to another
     * by passing in SKUs to be upgraded.
     *
     * @param activity active activity to launch our billing flow from
     * @param sku SKU (Product ID) to be purchased
     * @param upgradeSkusVarargs SKUs that the subscription can be upgraded from
     * @return true if launch is successful
     */
    fun launchBillingFlow(activity: Activity?, sku: String, vararg upgradeSkusVarargs: String) {
        val skuDetails = skuDetailsMap[sku]
        if (null != skuDetails) {

            val subscriptionOfferDetails = try {
                skuDetails.subscriptionOfferDetails?.get(0)?.offerToken ?: ""
            } catch (e: Exception) {
                ""
            }
            val productDetailsParamsList = listOf(
                BillingFlowParams.ProductDetailsParams.newBuilder()
                    // retrieve a value for "productDetails" by calling queryProductDetailsAsync()
                    .setProductDetails(skuDetails)
                    // to get an offer token, call ProductDetails.subscriptionOfferDetails()
                    // for a list of offers that are available to the user
                    .setOfferToken(subscriptionOfferDetails)
                    .build()
            )

            val billingFlowParamsBuilder = BillingFlowParams.newBuilder()
            billingFlowParamsBuilder.setProductDetailsParamsList(productDetailsParamsList)
            val upgradeSkus = arrayOf(*upgradeSkusVarargs)
            getPurchases(upgradeSkus, BillingClient.ProductType.SUBS) { heldSubscriptions ->
                when (heldSubscriptions.size) {
                    1 -> {
                        val purchase = heldSubscriptions[0]
                        billingFlowParamsBuilder.setSubscriptionUpdateParams(
                            BillingFlowParams.SubscriptionUpdateParams.newBuilder()
                                .setOldPurchaseToken(purchase.purchaseToken)
                                .build()
                        ).setIsOfferPersonalized(true)
                    }

                    0 -> {
                    }

                    else -> logger(
                        heldSubscriptions.size.toString() +
                                " subscriptions subscribed to. Upgrade not possible."
                    )
                }
                val br = billingClient.launchBillingFlow(
                    activity!!,
                    billingFlowParamsBuilder.build()
                )
                if (br.responseCode == BillingClient.BillingResponseCode.OK) {
                    billingFlowInProcess = (true)
                } else {
                    logger("Billing failed: + " + br.debugMessage)
                }

            }
        } else {
            logger("SkuDetails not found for: $sku")
        }
    }

    /**
     * Used internally to get purchases from a requested set of SKUs. This is particularly
     * important when changing subscriptions, as onPurchasesUpdated won't update the purchase state
     * of a subscription that has been upgraded from.
     *
     * @param skus skus to get purchase information for
     * @param skuType sku type, inapp or subscription, to get purchase information for.
     * @return purchases
     */
    private fun getPurchases(
        skus: Array<String>,
        skuType: String,
        resultListInvoke: (List<Purchase>) -> Unit
    ) {
        val queryProductDetailsParams =
            QueryPurchasesParams.newBuilder()
                .setProductType(skuType)
                .build()
        billingClient.queryPurchasesAsync(
            queryProductDetailsParams
        ) { billingResult, p1 ->
            val returnPurchasesList: MutableList<Purchase> = LinkedList()
            if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
                logger("Problem getting purchases: " + billingResult.debugMessage)
            } else {
                for (purchase in p1) {
                    for (sku in skus) {
                        for (purchaseSku in purchase.products) {
                            if (purchaseSku == sku) {
                                returnPurchasesList.add(purchase)
                            }
                        }
                    }
                }
            }
            resultListInvoke.invoke(returnPurchasesList)
        }

    }

    override fun onPurchasesUpdated(billingResult: BillingResult, list: MutableList<Purchase>?) {

        when (billingResult.responseCode) {
            BillingClient.BillingResponseCode.OK -> if (null != list) {
                processPurchaseList(list, null)
            } else logger("Null Purchase List Returned from OK response!")

            BillingClient.BillingResponseCode.USER_CANCELED -> logger("onPurchasesUpdated: User canceled the purchase")
            BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED -> logger("onPurchasesUpdated: The user already owns this item")
            BillingClient.BillingResponseCode.DEVELOPER_ERROR -> logger(

                "onPurchasesUpdated: Developer error means that Google Play " +
                        "does not recognize the configuration. If you are just getting started, " +
                        "make sure you have configured the application correctly in the " +
                        "Google Play Console. The SKU product ID must match and the APK you " +
                        "are using must be signed with release keys."
            )

            else -> logger("BillingResult [" + billingResult.responseCode + "]: " + billingResult.debugMessage)
        }


        billingFlowInProcess = false
    }


    /**
     * Returns whether or not the user has purchased a SKU. It does this by returning
     * a Flow that returns true if the SKU is in the PURCHASED state and
     * the Purchase has been acknowledged.
     * @return a Flow that observes the SKUs purchase state
     */
    fun isPurchased(sku: String): Boolean {
        return skuStateMap[sku] == SkuState.SKU_STATE_PURCHASED_AND_ACKNOWLEDGED
    }

    /**
     * Returns whether or not the user can purchase a SKU. It does this by returning
     * a Flow combine transformation that returns true if the SKU is in the UNSPECIFIED state, as
     * well as if we have skuDetails for the SKU. (SKUs cannot be purchased without valid
     * SkuDetails.)
     * @return a Flow that observes the SKUs purchase state
     */
    fun canPurchase(sku: String): Boolean {
        val skuDetailsFlow = skuDetailsMap[sku]
        val skuStateFlow = skuStateMap[sku]

        return skuStateFlow == SkuState.SKU_STATE_UNPURCHASED && skuDetailsFlow != null
    }

    fun resume() {
        logger("ON_RESUME")
        // this just avoids an extra purchase refresh after we finish a billing flow
        if (!billingFlowInProcess) {
            if (billingClient.isReady) {
                if (!knownInappSKUs.isNullOrEmpty()) {
                    refreshPurchases(knownInappSKUs, BillingClient.ProductType.INAPP)
                }
                if (!knownSubscriptionSKUs.isNullOrEmpty()) {
                    refreshPurchases(knownSubscriptionSKUs, BillingClient.ProductType.SUBS)
                }
            }
        }
    }
}